package org.nehc.algorithm.redissonutils;

import cn.hutool.extra.spring.SpringUtil;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.stats.CacheStats;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.*;
import org.springframework.util.StringUtils;

import java.time.Duration;
import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * Redisson 工具类主入口
 * 提供统一的Redis操作接口，内部委托给各个功能子类
 *
 * @author system
 */
@Slf4j
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@SuppressWarnings(value = {"unchecked", "rawtypes"})
public class RedissonUtils {

    private static final RedissonClient CLIENT = SpringUtil.getBean(RedissonClient.class);

    /**
     * 默认缓存存放地址
     */
    protected static final String APPLICATION_CACHE = CacheScope.APPLICATION.getCacheName();

    /**
     * 设置比较短的本地缓存过期时间:
     * 1. 减少本地缓存与Redis缓存数据不一致的时间窗口
     * 2. 在高并发场景下，即使Redis缓存被清除，本地缓存也会很快过期并重新加载
     */
    private static final int DEFAULT_CAFFEINE_EXPIRE_SECONDS = 5;

    /**
     * 获取客户端实例
     */
    public static RedissonClient getClient() {
        return CLIENT;
    }

    /**
     * 默认本地缓存最大容量
     */
    private static final int DEFAULT_CAFFEINE_MAX_SIZE = 10000;

    /**
     * 全局Caffeine本地缓存实例，用于减轻Redis负担
     * 采用复合键格式"cacheName:key"来区分不同缓存域的数据
     */
    private static final Cache<String, Object> CAFFEINE_CACHE = Caffeine.newBuilder()
            .expireAfterWrite(Duration.ofSeconds(DEFAULT_CAFFEINE_EXPIRE_SECONDS))
            .maximumSize(DEFAULT_CAFFEINE_MAX_SIZE)
            .recordStats()
            .build();

    /**
     * 获取本地缓存复合键
     *
     * @param cacheName 缓存域名称
     * @param key       缓存键
     * @return 复合键，格式为"cacheName:key"
     */
    private static String getLocalCacheKey(String cacheName, String key) {
        if (cacheName == null) {
            cacheName = APPLICATION_CACHE;
        }
        return cacheName + ":" + key;
    }

    /**
     * 从本地缓存获取值，如果不存在则从Redis获取并放入本地缓存
     *
     * @param cacheName   缓存域名称
     * @param key         缓存键
     * @param redisLoader 当本地缓存不存在时，用于从Redis加载数据的函数
     * @return 缓存值，可能为null
     */
    @SuppressWarnings("unchecked")
    public static <T> T getFromLocalCacheOrRedis(String cacheName, String key,
                                                  java.util.function.Function<String, T> redisLoader) {
        // 创建复合键
        String localCacheKey = getLocalCacheKey(cacheName, key);
        String redisKey = getKey(cacheName, key);

        // 尝试从本地缓存获取
        Object localValue = CAFFEINE_CACHE.getIfPresent(localCacheKey);
        if (localValue != null) {
            log.info("本地缓存命中: {}", localCacheKey);
            return (T) localValue;
        }

        // 本地缓存未命中，调用Redis加载器获取
        log.info("本地缓存未命中，从Redis获取: {}", redisKey);
        T redisValue = redisLoader.apply(key);

        // 如果Redis中存在数据，放入本地缓存
        if (redisValue != null) {
            CAFFEINE_CACHE.put(localCacheKey, redisValue);
            log.info("数据已加入本地缓存: {}", localCacheKey);
        }

        return redisValue;
    }

    /**
     * 限流
     *
     * @param key          限流key
     * @param rateType     限流类型
     * @param rate         速率
     * @param rateInterval 速率间隔
     * @return -1 表示失败
     */
    public static long rateLimiter(String key, RateType rateType, int rate, int rateInterval) {
        return RedissonRateLimiterUtils.checkRateLimit(key, rate, Duration.ofSeconds(rateInterval))
                ? RedissonRateLimiterUtils.availablePermits(key) : -1;
    }

    /**
     * 通用销毁队列 所有阻塞监听 报错(不支持延迟队列)
     */
    public static <T> boolean destroyQueue(String queueName) {
        return RedissonQueueUtils.destroyQueue(queueName);
    }

    /**
     * 添加普通队列数据
     *
     * @param queueName 队列名
     * @param data      数据
     */
    public static <T> boolean addQueueObject(String queueName, T data) {
        return RedissonQueueUtils.addQueueObject(queueName, data);
    }

    /**
     * 订阅队列(可订阅所有实现类 例如: 延迟 优先 有界 等)
     */
    public static <T> void subscribeBlockingQueue(String queueName, Consumer<T> consumer) {
        RedissonQueueUtils.subscribeBlockingQueue(queueName, consumer);
    }

    /**
     * 发布通道消息
     *
     * @param channelKey 通道key
     * @param msg        发送数据
     * @param consumer   自定义处理
     */
    public static <T> void publish(String channelKey, T msg, Consumer<T> consumer) {
        RedissonQueueUtils.publish(channelKey, msg, consumer);
    }

    /**
     * 发布通道消息
     *
     * @param channelKey 通道key
     * @param msg        发送数据
     */
    public static <T> void publish(String channelKey, T msg) {
        RedissonQueueUtils.publish(channelKey, msg);
    }

    /**
     * 订阅通道接收消息
     *
     * @param channelKey 通道key
     * @param clazz      消息类型
     * @param consumer   自定义处理
     */
    public static <T> void subscribe(String channelKey, Class<T> clazz, Consumer<T> consumer) {
        RedissonQueueUtils.subscribe(channelKey, clazz, consumer);
    }

    /**
     * 缓存基本的对象，Integer、String、实体类等
     *
     * @param key   缓存的键值
     * @param value 缓存的值
     */
    public static <T> void setCacheObject(final String cacheName, final String key, final T value) {
        RedissonObjectUtils.setCacheObject(cacheName, key, value);
    }

    /**
     * 缓存基本的对象，保留当前对象 TTL 有效期
     *
     * @param key       缓存的键值
     * @param value     缓存的值
     * @param isSaveTtl 是否保留TTL有效期(例如: set之前ttl剩余90 set之后还是为90)
     * @since Redis 6.X 以上使用 setAndKeepTTL 兼容 5.X 方案
     */
    public static <T> void setCacheObject(final String cacheName, final String key, final T value, final boolean isSaveTtl) {
        RedissonObjectUtils.setCacheObject(cacheName, key, value, isSaveTtl);
    }

    /**
     * 缓存基本的对象，Integer、String、实体类等
     *
     * @param key      缓存的键值
     * @param value    缓存的值
     * @param duration 时间
     */
    public static <T> void setCacheObject(final String cacheName, final String key, final T value, final Duration duration) {
        RedissonObjectUtils.setCacheObject(cacheName, key, value, duration);
    }

    /**
     * 缓存基本的对象 —— 已有则返回缓存失败，否则返回缓存成功
     *
     * @param key      缓存的键值
     * @param value    缓存的值
     * @param duration 超时时间
     * @return java.lang.Boolean 缓存是否成功
     */
    public static <T> Boolean setObjIfAbsent(final String cacheName, final String key, final T value, final Duration duration) {
        return RedissonObjectUtils.setObjIfAbsent(cacheName, key, value, duration);
    }

    /**
     * 注册对象监听器
     * <p>
     * key 监听器需开启 `notify-keyspace-events` 等 redis 相关配置
     *
     * @param key      缓存的键值
     * @param listener 监听器配置
     */
    public static <T> void addObjectListener(final String cacheName, final String key, final ObjectListener listener) {
        RedissonObjectUtils.addObjectListener(cacheName, key, listener);
    }

    /**
     * 设置有效时间
     *
     * @param key     Redis键
     * @param timeout 超时时间
     * @return true=设置成功；false=设置失败
     */
    public static boolean expire(final String cacheName, final String key, final long timeout) {
        return RedissonObjectUtils.expire(cacheName, key, timeout);
    }

    /**
     * 设置有效时间
     *
     * @param key      Redis键
     * @param duration 超时时间
     * @return true=设置成功；false=设置失败
     */
    public static boolean expire(final String cacheName, final String key, final Duration duration) {
        return RedissonObjectUtils.expire(cacheName, key, duration);
    }

    /**
     * 获得缓存的基本对象。
     *
     * @param cacheName 缓存名称
     * @param key       缓存键值
     * @return 缓存键值对应的数据
     */
    public static <T> T getCacheObject(final String cacheName, final String key) {
        return RedissonObjectUtils.getCacheObject(cacheName, key);
    }

    /**
     * 模糊查询获得缓存的基本对象
     *
     * @param cacheName 缓存键值
     * @param isRight   是否右匹配，否则左匹配
     * @return java.util.List<T> 缓存键值对应的数据
     * @since 2023/12/15 10:34
     */
    public static <T> List<T> getCacheObjectRight(final String cacheName, Boolean isRight) {
        return RedissonObjectUtils.getCacheObjectRight(cacheName, isRight);
    }

    /**
     * 获得key剩余存活时间
     *
     * @param key 缓存键值
     * @return 剩余存活时间
     */
    public static <T> long getTimeToLive(final String cacheName, final String key) {
        return RedissonObjectUtils.getTimeToLive(cacheName, key);
    }

    /**
     * 删除单个对象
     *
     * @param key 缓存的键值
     */
    public static boolean deleteObject(final String cacheName, final String key) {
        return RedissonObjectUtils.deleteObject(cacheName, key);
    }

    /**
     * 删除集合对象
     *
     * @param collection 多个对象
     */
    public static void deleteObject(final Collection collection) {
        RedissonObjectUtils.deleteObject(collection);
    }

    /**
     * 检查缓存对象是否存在
     *
     * @param key 缓存的键值
     */
    public static boolean isExistsObject(final String cacheName, final String key) {
        return RedissonObjectUtils.isExistsObject(cacheName, key);
    }

    /**
     * 缓存List数据
     *
     * @param key      缓存的键值
     * @param dataList 待缓存的List数据
     * @return 缓存的对象
     */
    public static <T> boolean setCacheList(final String cacheName, final String key, final List<T> dataList) {
        return RedissonCollectionUtils.setCacheList(cacheName, key, dataList);
    }

    /**
     * 缓存List数据
     *
     * @param key       缓存的键值
     * @param data      待缓存的data数据
     * @param finalSize 指定最终的长度
     * @return 缓存的对象
     */
    public static <T> boolean listRightPush(final String cacheName, final String key, final T data, final int finalSize) {
        return RedissonCollectionUtils.listRightPush(cacheName, key, data, finalSize);
    }

    /**
     * 注册List监听器
     * <p>
     * key 监听器需开启 `notify-keyspace-events` 等 redis 相关配置
     *
     * @param key      缓存的键值
     * @param listener 监听器配置
     */
    public static <T> void addListListener(final String cacheName, final String key, final ObjectListener listener) {
        RedissonCollectionUtils.addListListener(cacheName, key, listener);
    }

    /**
     * 获得缓存的list对象，优先从本地缓存获取，本地缓存未命中则从Redis获取
     *
     * @param cacheName 缓存名称
     * @param key       缓存的键值
     * @return 缓存键值对应的数据
     */
    public static <T> List<T> getCacheList(final String cacheName, final String key) {
        return RedissonCollectionUtils.getCacheList(cacheName, key);
    }

    /**
     * 缓存Set
     *
     * @param key     缓存键值
     * @param dataSet 缓存的数据
     * @return 缓存数据的对象
     */
    public static <T> boolean setCacheSet(final String cacheName, final String key, final Set<T> dataSet) {
        return RedissonCollectionUtils.setCacheSet(cacheName, key, dataSet);
    }

    /**
     * 注册Set监听器
     * <p>
     * key 监听器需开启 `notify-keyspace-events` 等 redis 相关配置
     *
     * @param key      缓存的键值
     * @param listener 监听器配置
     */
    public static <T> void addSetListener(final String cacheName, final String key, final ObjectListener listener) {
        RedissonCollectionUtils.addSetListener(cacheName, key, listener);
    }

    /**
     * 获得缓存的set
     *
     * @param key 缓存的key
     * @return set对象
     */
    public static <T> Set<T> getCacheSet(final String cacheName, final String key) {
        return RedissonCollectionUtils.getCacheSet(cacheName, key);
    }

    /**
     * 异步获得缓存的set
     *
     * @param key 缓存的key
     * @return set对象
     */
    public static <T> Set<T> getCacheSetAsync(final String cacheName, final String key) {
        return RedissonCollectionUtils.getCacheSetAsync(cacheName, key);
    }

    /**
     * 缓存Map
     *
     * @param key     缓存的键值
     * @param dataMap 缓存的数据
     */
    public static <K, T> void setCacheMap(final String cacheName, final String key, final Map<K, T> dataMap, Duration duration) {
        RedissonCollectionUtils.setCacheMap(cacheName, key, dataMap, duration);
    }

    /**
     * 缓存Map
     *
     * @param key     缓存的键值
     * @param dataMap 缓存的数据
     */
    public static <K, T> void setCacheMap(final String cacheName, final String key, final Map<K, T> dataMap) {
        RedissonCollectionUtils.setCacheMap(cacheName, key, dataMap);
    }

    /**
     * 注册Map监听器
     * <p>
     * key 监听器需开启 `notify-keyspace-events` 等 redis 相关配置
     *
     * @param key      缓存的键值
     * @param listener 监听器配置
     */
    public static <T> void addMapListener(final String cacheName, final String key, final ObjectListener listener) {
        RedissonCollectionUtils.addMapListener(cacheName, key, listener);
    }

    /**
     * 获得缓存的Map
     *
     * @param key 缓存的键值
     * @return map对象
     */
    public static <K, T> Map<K, T> getCacheMap(final String cacheName, final String key) {
        return RedissonCollectionUtils.getCacheMap(cacheName, key);
    }

    /**
     * 获得缓存Map的key列表
     *
     * @param key 缓存的键值
     * @return key列表
     */
    public static <T> Set<String> getCacheMapKeySet(final String cacheName, final String key) {
        return RedissonCollectionUtils.getCacheMapKeySet(cacheName, key);
    }

    /**
     * 往Hash中存入数据
     *
     * @param key   Redis键
     * @param hKey  Hash键
     * @param value 值
     */
    public static <T> void setCacheMapValue(final String cacheName, final String key, final String hKey, final T value) {
        RedissonCollectionUtils.setCacheMapValue(cacheName, key, hKey, value);
    }

    /**
     * 获取Hash中的数据
     *
     * @param key  Redis键
     * @param hKey Hash键
     * @return Hash中的对象
     */
    public static <T> T getCacheMapValue(final String cacheName, final String key, final String hKey) {
        return RedissonCollectionUtils.getCacheMapValue(cacheName, key, hKey);
    }

    /**
     * 删除Hash中的数据
     *
     * @param key  Redis键
     * @param hKey Hash键
     * @return Hash中的对象
     */
    public static <T> T delCacheMapValue(final String cacheName, final String key, final String hKey) {
        return RedissonCollectionUtils.delCacheMapValue(cacheName, key, hKey);
    }

    /**
     * 获取多个Hash中的数据
     *
     * @param key   Redis键
     * @param hKeys Hash键集合
     * @return Hash对象集合
     */
    public static <K, V> Map<K, V> getMultiCacheMapValue(final String cacheName, final String key, final Set<K> hKeys) {
        return RedissonCollectionUtils.getMultiCacheMapValue(cacheName, key, hKeys);
    }

    /**
     * 设置原子值
     *
     * @param key   Redis键
     * @param value 值
     */
    public static void setAtomicValue(final String cacheName, final String key, long value) {
        RedissonAtomicUtils.setAtomicValue(cacheName, key, value);
    }

    /**
     * 设置原子值及过期时间
     *
     * @param key   Redis键
     * @param value 值
     */
    public static void setAtomicValue(final String cacheName, final String key, long value, Duration duration) {
        RedissonAtomicUtils.setAtomicValue(cacheName, key, value, duration);
    }

    /**
     * 获取原子值
     *
     * @param key Redis键
     * @return 当前值
     */
    public static long getAtomicValue(final String cacheName, final String key) {
        return RedissonAtomicUtils.getAtomicValue(cacheName, key);
    }

    /**
     * 递增原子值
     *
     * @param key Redis键
     * @return 当前值
     */
    public static long incrAtomicValue(final String cacheName, final String key) {
        return RedissonAtomicUtils.incrAtomicValue(cacheName, key);
    }

    /**
     * 递减原子值
     *
     * @param key Redis键
     * @return 当前值
     */
    public static long decrAtomicValue(final String cacheName, final String key) {
        return RedissonAtomicUtils.decrAtomicValue(cacheName, key);
    }

    /**
     * 获得缓存的基本对象列表
     *
     * @param pattern 字符串前缀
     * @return 对象列表
     */
    public static Collection<String> keys(final String pattern) {
        Stream<String> stream = CLIENT.getKeys().getKeysStreamByPattern(pattern);
        return stream.collect(Collectors.toList());
    }

    /**
     * 删除缓存的基本对象列表
     *
     * @param pattern 字符串前缀
     */
    public static void deleteKeys(final String pattern) {
        CLIENT.getKeys().deleteByPattern(pattern);
    }

    /**
     * 检查redis中是否存在key
     *
     * @param key 键
     */
    public static Boolean hasKey(final String cacheName, final String key) {
        RKeys rKeys = CLIENT.getKeys();
        return rKeys.countExists(getKey(cacheName, key)) > 0;
    }

    /**
     * 获取分布式锁，支持自定义等待和超时时间
     *
     * @param cacheName 缓存名
     * @param key       锁的key
     * @param waitTime  最大等待时间
     * @param leaseTime 锁自动释放时间
     * @return Boolean 是否获取成功
     */
    public static Boolean tryLock(final String cacheName, final String key,
                                  Duration waitTime, Duration leaseTime) {
        return RedissonLockUtils.tryLock(cacheName, key, waitTime, leaseTime);
    }

    /**
     * 获取分布式锁，支持自定义超时时间（毫秒）
     *
     * @param cacheName    缓存名
     * @param key          锁的key
     * @param timeoutMills 锁超时时间（毫秒）
     * @return Boolean 是否获取成功
     */
    public static Boolean tryLock(final String cacheName, final String key, Integer timeoutMills) {
        return RedissonLockUtils.tryLock(cacheName, key, timeoutMills);
    }

    /**
     * 获取分布式锁
     *
     * @param cacheName 缓存名
     * @param key       锁的key
     * @return Boolean 是否获取成功
     */
    public static Boolean tryLock(final String cacheName, final String key) {
        return RedissonLockUtils.tryLock(cacheName, key);
    }

    /**
     * 获取可重入锁实例
     *
     * @param cacheName 缓存名
     * @param key       锁的key
     * @return RLock 可重入锁实例
     */
    public static RLock getReentrantLock(final String cacheName, final String key) {
        return RedissonLockUtils.getReentrantLock(cacheName, key);
    }

    /**
     * 释放分布式锁
     *
     * @param cacheName 缓存名
     * @param key       锁的key
     */
    public static void unLock(final String cacheName, final String key) {
        RedissonLockUtils.unLock(cacheName, key);
    }

    /**
     * 获取缓存键名，多数据源下增加数据源名称前缀
     *
     * @return java.lang.String
     * @author system
     * @since 2023/2/23 15:50
     */
    protected static String getKey(String cacheName, String key) {
        StringBuilder sbr = new StringBuilder(cacheName);
        if (!StringUtils.hasText(cacheName)) {
            cacheName = APPLICATION_CACHE;
        }
        if (cacheName.endsWith(":")) {
            sbr.append(key);
        } else {
            sbr.append(":").append(key);
        }
        return sbr.toString();
    }



    /**
     * 批量获取缓存对象
     *
     * @param cacheName 缓存名称
     * @param keys      缓存键值列表
     * @return 缓存键值对应的数据映射
     */
    public static <T> Map<String, T> getCacheObjects(final String cacheName, final List<String> keys) {
        return RedissonObjectUtils.getCacheObjects(cacheName, keys);
    }

    /**
     * 批量缓存对象
     *
     * @param cacheName   缓存名称
     * @param keyValueMap 键值映射
     * @param duration    过期时间（可选）
     */
    public static <T> void setCacheObjects(final String cacheName, final Map<String, T> keyValueMap, final Duration duration) {
        RedissonObjectUtils.setCacheObjects(cacheName, keyValueMap, duration);
    }

    /**
     * 批量缓存对象（无过期时间）
     *
     * @param cacheName   缓存名称
     * @param keyValueMap 键值映射
     */
    public static <T> void setCacheObjects(final String cacheName, final Map<String, T> keyValueMap) {
        RedissonObjectUtils.setCacheObjects(cacheName, keyValueMap);
    }

    /**
     * 原子性地替换或创建缓存列表
     *
     * @param cacheName 缓存名称
     * @param key       缓存键值
     * @param dataList  待缓存的List数据
     * @param duration  过期时间（可选）
     * @return 操作是否成功
     */
    public static <T> boolean replaceCacheList(final String cacheName, final String key, final List<T> dataList, final Duration duration) {
        return RedissonCollectionUtils.replaceCacheList(cacheName, key, dataList, duration);
    }

    /**
     * 原子性地替换或创建缓存列表（无过期时间）
     *
     * @param cacheName 缓存名称
     * @param key       缓存键值
     * @param dataList  待缓存的List数据
     * @return 操作是否成功
     */
    public static <T> boolean replaceCacheList(final String cacheName, final String key, final List<T> dataList) {
        return RedissonCollectionUtils.replaceCacheList(cacheName, key, dataList);
    }

    /**
     * 缓存List数据，同时更新本地缓存和Redis缓存
     *
     * @param cacheName 缓存名称
     * @param key       缓存的键值
     * @param dataList  待缓存的List数据
     * @param duration  过期时间
     * @return 操作是否成功
     */
    public static <T> boolean setCacheList(final String cacheName, final String key, final List<T> dataList, final Duration duration) {
        return RedissonCollectionUtils.setCacheList(cacheName, key, dataList, duration);
    }


}
